<?php
/**
 * Zhub - A PHP Framework For API Developer
 *
 * This file is part of the Zhub package.
 *
 * (c) YiLong Fan <fantiq@163.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @author fanyilong <fantiq@163.com>
 * @copyright 2017 fanyilong. All rights reserved.
 * @package Zhub
 * @date 18-3-15
 */

namespace Zhub\Event;

use Closure;
use Zhub\Event\Exceptions\NotFoundEventException;

abstract class Event
{
    private $eventCollection = [];

    private $results = [];

    private $chain = [];

    private static $instance = [];


    public function bind($eventId, Closure $handle)
    {
        $this->eventCollection[$eventId] = $handle;
        $this->results[$eventId] = null;
        return $eventId;
    }

    public function bindIf($eventId, Closure $handle)
    {
        if (array_key_exists($eventId, $this->eventCollection)) {
            return null;
        }
        return $this->bind($eventId, $handle);
    }

    public function bindObserved($eventId, Closure ...$observed)
    {}

    public function fire($eventId, array $parameters = [])
    {
        $handle = $this->eventCollection[$eventId] ?? null;
        if (!is_callable($handle)) {
            throw new NotFoundEventException($eventId, static::class);
        }
        $result = call_user_func_array($handle, $parameters);
        $this->results[$eventId] = is_array($result) ? $result : [$result];
        return $result;
    }

    public function getResult($eventId, int $idx = 0)
    {
        $result = $this->results[$eventId] ?? null;
        if ($idx < 0 || $result === null) {
            return $result;
        }
        return $result[$idx] ?? null;
    }

    public function getResults():array
    {
        return $this->results;
    }

    public function orderChain(... $eventIds)
    {
        $this->chain = $eventIds;
    }

    public function fireChain(...$arguments)
    {
        foreach ($this->chain as $eventId) {
            $this->fire($eventId, $arguments);
            $arguments = $this->getResult($eventId, -1);
        }

        return $arguments;
    }

    /**
     * @return Event
     * 获取事件的实例化对象 单例
     */
    public static function getInstance():self
    {
        if (!array_key_exists(static::class, self::$instance)) {
            self::$instance[static::class] = new static();
        }
        return self::$instance[static::class];
    }

    public static function copy():self
    {
        $instance = clone self::getInstance();
        $instance->eventCollection = [];
        $instance->results = [];
        $instance->chain = [];
        return $instance;
    }

    /**
     * Event constructor.
     * 此类不能实例化 只能通过 newInstance 方法来获取单例
     * 当然你可以 clone
     */
    final private function __construct()
    {
        $this->boot();
    }

    /**
     * @return mixed
     * 实例化事件方法后的自动调用
     */
    abstract protected function boot();
}
